package cmdrunner

import (
	"context"
	"fmt"
	"io/ioutil"
	"os/exec"
	"strings"
	"time"

	"github.com/project-flogo/core/activity"
)

type Activity struct {
}

func init() {
	_ = activity.Register(&Activity{}, New) //activity.Register(&Activity{}, New) to create instances using factory method 'New'
}

func New(ctx activity.InitContext) (activity.Activity, error) {
	return &Activity{}, nil
}

var activityMd = activity.ToMetadata(&Settings{}, &Input{}, &Output{})

func (a *Activity) Metadata() *activity.Metadata {
	return activityMd
}

func (a *Activity) Eval(ctx activity.Context) (bool, error) {
	input := Input{}
	err := ctx.GetInputObject(&input)
	if err != nil {
		return true, err
	}
	cmds := strings.Split(input.Cmd, " ")
	if len(cmds) == 0 {
		return true, fmt.Errorf("invalid cmd: %s", input.Cmd)
	}
	timeout, err := toDuration(input.Timeout)
	if err != nil {
		return true, err
	}
	cmdCtx, _ := context.WithTimeout(context.Background(), timeout)
	var cmd *exec.Cmd
	if len(cmds) > 1 {
		cmd = exec.CommandContext(cmdCtx, cmds[0], cmds[1:]...)
	} else {
		cmd = exec.CommandContext(cmdCtx, cmds[0])
	}
	errs := []error{}
	output := Output{}

	// get stdout and stderr pipe
	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		errx := fmt.Errorf("get stdout error: %+v", err)
		errs = append(errs, errx)
	}
	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		errx := fmt.Errorf("get stderr error: %+v", err)
		errs = append(errs, errx)
	}

	// start cmd
	err = cmd.Start()
	if err != nil {
		output := Output{}
		output.Err = err.Error()
		err := ctx.SetOutputObject(&output)
		return true, err
	}

	// start read stdout and stderr
	var stdout []byte
	if stdoutPipe != nil {
		stdout, err = ioutil.ReadAll(stdoutPipe)
		if err != nil {
			errx := fmt.Errorf("read stdout error: %+v", err)
			errs = append(errs, errx)
		}
	}
	var stderr []byte
	if stderrPipe != nil {
		stderr, err = ioutil.ReadAll(stderrPipe)
		if err != nil {
			errx := fmt.Errorf("read stderr error: %+v", err)
			errs = append(errs, errx)
		}
	}

	err = cmd.Wait()
	if err != nil {
		errx := fmt.Errorf("run error: %+v", err)
		errs = append(errs, errx)
	}

	output.Err = combineErrs(errs)
	output.Stdout = stdout
	output.Stderr = stderr

	err = ctx.SetOutputObject(&output)
	return true, err
}

func toDuration(s string) (time.Duration, error) {
	return time.Second * 10, nil
}

func combineErrs(errs []error) string {
	errStrs := []string{}
	for _, err := range errs {
		str := err.Error()
		errStrs = append(errStrs, str)
	}
	return strings.Join(errStrs, "\n")
}
